#include "Geo.h"

#include <glad/glad.h>
#include <GLFW/glfw3.h>

#include "OpenGL/stb_image.h"

unsigned int loadTexture(char const* path)
{
    unsigned int textureID;
    glGenTextures(1, &textureID);

    int width, height, nrComponents;
    unsigned char* data = stbi_load(path, &width, &height, &nrComponents, 0);
    if (data)
    {
        GLenum format;
        if (nrComponents == 1)
            format = GL_RED;
        else if (nrComponents == 3)
            format = GL_RGB;
        else if (nrComponents == 4)
            format = GL_RGBA;

        glBindTexture(GL_TEXTURE_2D, textureID);
        glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, format, GL_UNSIGNED_BYTE, data);
        glGenerateMipmap(GL_TEXTURE_2D);

        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

        stbi_image_free(data);
    }
    else
    {
        std::cout << "Texture failed to load at path: " << path << std::endl;
        stbi_image_free(data);
    }

    return textureID;
}

void setLightShader(Shader* lightingShader, glm::vec3 pointLightPositions[])
{
    lightingShader->use();
    lightingShader->setFloat("material.shininess", 32.0f);
    lightingShader->setInt("material.diffuse", 0);
    lightingShader->setInt("material.specular", 1);

    // directional light
    lightingShader->setVec3("dirLight.direction", -0.2f, -1.0f, -0.3f);
    lightingShader->setVec3("dirLight.ambient", 0.05f, 0.05f, 0.05f);
    lightingShader->setVec3("dirLight.diffuse", 0.4f, 0.4f, 0.4f);
    lightingShader->setVec3("dirLight.specular", 0.5f, 0.5f, 0.5f);
    // point light 1
    lightingShader->setVec3("pointLights[0].position", pointLightPositions[0]);
    lightingShader->setVec3("pointLights[0].ambient", 0.05f, 0.05f, 0.05f);
    lightingShader->setVec3("pointLights[0].diffuse", 0.8f, 0.8f, 0.8f);
    lightingShader->setVec3("pointLights[0].specular", 1.0f, 1.0f, 1.0f);
    lightingShader->setFloat("pointLights[0].constant", 1.0f);
    lightingShader->setFloat("pointLights[0].linear", 0.09);
    lightingShader->setFloat("pointLights[0].quadratic", 0.032);
    // point light 2
    lightingShader->setVec3("pointLights[1].position", pointLightPositions[1]);
    lightingShader->setVec3("pointLights[1].ambient", 0.05f, 0.05f, 0.05f);
    lightingShader->setVec3("pointLights[1].diffuse", 0.8f, 0.8f, 0.8f);
    lightingShader->setVec3("pointLights[1].specular", 1.0f, 1.0f, 1.0f);
    lightingShader->setFloat("pointLights[1].constant", 1.0f);
    lightingShader->setFloat("pointLights[1].linear", 0.09);
    lightingShader->setFloat("pointLights[1].quadratic", 0.032);
    // point light 3
    lightingShader->setVec3("pointLights[2].position", pointLightPositions[2]);
    lightingShader->setVec3("pointLights[2].ambient", 0.05f, 0.05f, 0.05f);
    lightingShader->setVec3("pointLights[2].diffuse", 0.8f, 0.8f, 0.8f);
    lightingShader->setVec3("pointLights[2].specular", 1.0f, 1.0f, 1.0f);
    lightingShader->setFloat("pointLights[2].constant", 1.0f);
    lightingShader->setFloat("pointLights[2].linear", 0.09);
    lightingShader->setFloat("pointLights[2].quadratic", 0.032);
    // point light 4
    lightingShader->setVec3("pointLights[3].position", pointLightPositions[3]);
    lightingShader->setVec3("pointLights[3].ambient", 0.05f, 0.05f, 0.05f);
    lightingShader->setVec3("pointLights[3].diffuse", 0.8f, 0.8f, 0.8f);
    lightingShader->setVec3("pointLights[3].specular", 1.0f, 1.0f, 1.0f);
    lightingShader->setFloat("pointLights[3].constant", 1.0f);
    lightingShader->setFloat("pointLights[3].linear", 0.09);
    lightingShader->setFloat("pointLights[3].quadratic", 0.032);
    //spotlight
    lightingShader->setVec3("spotLight.ambient", 0.0f, 0.0f, 0.0f);
    lightingShader->setVec3("spotLight.diffuse", 1.0f, 1.0f, 1.0f);
    lightingShader->setVec3("spotLight.specular", 1.0f, 1.0f, 1.0f);
    lightingShader->setFloat("spotLight.constant", 1.0f);
    lightingShader->setFloat("spotLight.linear", 0.09);
    lightingShader->setFloat("spotLight.quadratic", 0.032);
    lightingShader->setFloat("spotLight.cutOff", glm::cos(glm::radians(12.5f)));
    lightingShader->setFloat("spotLight.outerCutOff", glm::cos(glm::radians(15.0f)));


}
void Geo::init()
{
    glGenVertexArrays(1, &cubeVAO);
    glGenBuffers(1, &cubeVBO);

    glBindBuffer(GL_ARRAY_BUFFER, cubeVBO);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

    glBindVertexArray(cubeVAO);
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, stride * sizeof(float), (void*)0);
    glEnableVertexAttribArray(0);
    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, stride * sizeof(float), (void*)(3 * sizeof(float)));
    glEnableVertexAttribArray(1);
    glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, stride * sizeof(float), (void*)(6 * sizeof(float)));
    glEnableVertexAttribArray(2);

    // second, configure the light's VAO
    glGenVertexArrays(1, &lightVAO);
    glBindVertexArray(lightVAO);

    glBindBuffer(GL_ARRAY_BUFFER, cubeVBO);

    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, stride * sizeof(float), (void*)0);
    glEnableVertexAttribArray(0);

    // load textures
    diffuseMap = loadTexture("resources/textures/container.jpg");
    specularMap = loadTexture("resources/textures/container.jpg");

    setLightShader(&(*lightingShader), pointLightPositions);

    for (unsigned int i = 0; i < cubeNum; i++)
    {
        glm::mat4 model = glm::mat4(1.0f);
        model = glm::translate(model, cubePositions[i]);
        model = glm::scale(model, glm::vec3(0.5f));
        Models.push_back(model);
    }
}

void Geo::draw(Camera* camera)
{
    lightingShader->use();
    lightingShader->setVec3("viewPos", camera->Position);

    // spotLight
    lightingShader->setVec3("spotLight.position", camera->Position);
    lightingShader->setVec3("spotLight.direction", camera->Front);

    // view/projection transformations
    glm::mat4 projection = glm::perspective(glm::radians(camera->Zoom), (float)1400 / (float)1000, 0.1f, 100.0f);
    glm::mat4 view = camera->GetViewMatrix();
    lightingShader->setMat4("projection", projection);
    lightingShader->setMat4("view", view);

    // world transformation
    glm::mat4 model = glm::mat4(1.0f);
    lightingShader->setMat4("model", model);

    // bind diffuse map
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, diffuseMap);
    // bind specular map
    glActiveTexture(GL_TEXTURE1);
    glBindTexture(GL_TEXTURE_2D, specularMap);

    // render containers
    glBindVertexArray(cubeVAO);

    for (unsigned int i = 0; i < cubeNum; i++)
    {
        lightingShader->setMat4("model", Models[i]);
        glDrawArrays(GL_TRIANGLES, 0, length);
    }
}

void Geo::setShader(Camera* camera)
{
    lightingShader->use();
    lightingShader->setVec3("viewPos", camera->Position);

    // spotLight
    lightingShader->setVec3("spotLight.position", camera->Position);
    lightingShader->setVec3("spotLight.direction", camera->Front);

    // view/projection transformations
    glm::mat4 projection = glm::perspective(glm::radians(camera->Zoom), (float)1400 / (float)1000, 0.1f, 100.0f);
    glm::mat4 view = camera->GetViewMatrix();
    lightingShader->setMat4("projection", projection);
    lightingShader->setMat4("view", view);

    // world transformation
    glm::mat4 model = glm::mat4(1.0f);
    lightingShader->setMat4("model", model);
}

void Geo::drawCulTime(Camera* camera)
{
    float Time = glfwGetTime();
    lightingShader->use();
    lightingShader->setVec3("viewPos", camera->Position);

    // spotLight
    lightingShader->setVec3("spotLight.position", camera->Position);
    lightingShader->setVec3("spotLight.direction", camera->Front);

    // view/projection transformations
    glm::mat4 projection = glm::perspective(glm::radians(camera->Zoom), (float)1400 / (float)1000, 0.1f, 100.0f);
    glm::mat4 view = camera->GetViewMatrix();
    lightingShader->setMat4("projection", projection);
    lightingShader->setMat4("view", view);

    // world transformation
    glm::mat4 model = glm::mat4(1.0f);
    lightingShader->setMat4("model", model);
    float t = glfwGetTime();
    std::cout << "set shader:" << t - Time << std::endl;

    Time = glfwGetTime();
    // bind diffuse map
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, diffuseMap);
    // bind specular map
    glActiveTexture(GL_TEXTURE1);
    glBindTexture(GL_TEXTURE_2D, specularMap);
    t = glfwGetTime();
    std::cout << "bind diffuse:" << t - Time << std::endl;

    Time = glfwGetTime();
    // render containers
    glBindVertexArray(cubeVAO);

    for (unsigned int i = 0; i < cubeNum; i++)
    {
        lightingShader->setMat4("model", Models[i]);
        glDrawArrays(GL_TRIANGLES, 0, length);
    }
    /*t = glfwGetTime();
    std::cout << "render:" << t - Time << std::endl;*/
}

void Geo::addCube(glm::vec3 cubePos)
{
	if (cubeNum < maxCube)
	{
		cubePositions[cubeNum] = cubePos;
        glm::mat4 model = glm::mat4(1.0f);
        model = glm::translate(model, cubePos);
        model = glm::scale(model, glm::vec3(0.5f));
        Models.push_back(model);
		cubeNum++;
	}
}

void Geo::delCube()
{
    if (cubeNum > 0) {
        cubeNum -= 1;
        Models.pop_back();
    }
}
